#include <QGLWidget>
#include <QtOpenGL/QtOpenGL>
#include <QColor>
#include <QSizePolicy>
#include <QFileInfo>
#include <iostream>
#include <fstream>
#include <cstdio>
//#include <string>
//#include <vector>

#include "MatrixDrawing.hpp"

#include <GL/glu.h>
#include <GL/gl.h>
#include <QMatrix4x4>



void MatrixDrawing::Initalvalue()
{
    u[0]=v[1]=n[2]=1.0f;
    u[1]=u[2]=v[0]=v[2]=n[1]=n[0]=0.0f;

    for(int i=0;i<3;i++)
    {
        rotation[i]=0.0f;
        last_rotation[i]=0.0f;
        translate[i]=0.0f;
        last_translate[i]=0.0f;
        uipara.rotation[i]=0.0f;
        uipara.translation[i]=0.0f;
    }
    fovy = 60.0/180.0*3.1415926;
    aspect = width()/height();
	modelviewmat.IdentityMatrix();
	GLfloat temp[16];
	getTMatrix(-(para.pmin[0]+para.pmax[0])/2,-(para.pmin[1]+para.pmax[1])/2,-(para.pmax[2]+para.pmin[2])/2-para.distance,temp);
	modelviewmat.MatrixMult(temp);
	projectionmat.IdentityMatrix();
	CalcProjectionMatrix();
}

MatrixDrawing::userparameter::userparameter()
{
    facefront = GL_CCW;
    for(int i = 0;i<3;i++)
    {
        color[i]=255;
        rotation[i]=0.0f;
        translation[i]=0.0f;
    }
    nearer = 0.1;
    farer = 6.0;
    primitive = GL_LINE;
}
MatrixDrawing::matrix::matrix()
{
    IdentityMatrix();
}
void MatrixDrawing::matrix::IdentityMatrix()
{
    for(int ii=0;ii<16;ii++)
        m[ii]=0.0f;
    for(int ii = 0;ii<4;ii++)
        m[5*ii]=1.0f;
}
void MatrixDrawing::matrix::MatrixMult(GLfloat* b) // a = b* a
{
    float temp[16];
	for(int ii=0; ii<16; ii++){
        temp[ii]=m[ii];
        m[ii] = 0.0f;
	}
    for(int ii=0; ii<4; ii++)
        for (int jj = 0;jj<4;jj++)
            for(int kk = 0;kk<4;kk++)
                m[4*jj+ii] += b[4*kk+ii]*temp[4*jj+kk];
}

void MatrixDrawing::getTMatrix(float x, float y, float z, GLfloat* temp)
{
    glPushMatrix();
    glMatrixMode(GL_MODELVIEW_MATRIX);
    glLoadIdentity();
    glTranslatef(x,y,z);
    glGetFloatv(GL_MODELVIEW_MATRIX,temp);
    glPopMatrix();
}

void MatrixDrawing::getRMatrix(float x, float y, float z, GLfloat *temp)
{
    glPushMatrix();
    glMatrixMode(GL_MODELVIEW_MATRIX);
    glLoadIdentity();
    glRotatef(x,1.0f,0.0f,0.0f);
    glRotatef(y,0.0f,1.0f,0.0f);
    glRotatef(z,0.0f,0.0f,1.0f);
    glGetFloatv(GL_MODELVIEW_MATRIX,temp);
    glPopMatrix();
}

void MatrixDrawing::CalcProjectionMatrix()
{
    GLfloat f = 1/tan(fovy/2);
    projectionmat.IdentityMatrix();

    projectionmat.m[0]=f/aspect;
    projectionmat.m[5]=f;
    projectionmat.m[10]=(uipara.farer+uipara.nearer)/(uipara.nearer-uipara.farer);
    projectionmat.m[11]=-1.0;
    projectionmat.m[14]=2*uipara.farer*uipara.nearer/(uipara.nearer-uipara.farer);
    projectionmat.m[15]=0.0;
}


void MatrixDrawing::CameraRotation()
{
    GLfloat temp[16];
    getRMatrix(rotation[0],rotation[1],rotation[2],temp);
    modelviewmat.MatrixMult(temp);
//update camera
    for(int i=0;i<3;i++)
    {
        u[i]=modelviewmat.m[4*i];
        v[i]=modelviewmat.m[4*i+1];
        n[i]=modelviewmat.m[4*i+2];
    }
}

 inline float fmax(float x, float y)
{
   return x>y?x:y;
}
 inline float fmin(float x, float y)
 {
     return x<y?x:y;
 }

void MatrixDrawing::readfile(const char *FileName)
{
    Initalvalue();
    char ch;
    FILE* fp = fopen(FileName,"r");
    if (fp==NULL)
    {
        printf("ERROR: unable to open TriObj [%s]!\n",FileName);
        exit(1);
    }
    fscanf(fp, "%c", &ch);
	while(ch!= '\n') // skip the first line
        fscanf(fp, "%c", &ch);

    fscanf(fp,"# triangles = %d\n", &para.trinum); // read # of triangles
    fscanf(fp,"Material count = %d\n", &para.marnum); // read material count
    //
    para.mat.resize(para.marnum);
    for(int i=0; i<para.marnum; i++)
    {
        fscanf(fp, "ambient color %f %f %f\n", &para.mat[i].acolor[0], &para.mat[i].acolor[1], &para.mat[i].acolor[2]);
        fscanf(fp, "diffuse color %f %f %f\n", &para.mat[i].dcolor[0], &para.mat[i].dcolor[1], &para.mat[i].dcolor[2]);
        fscanf(fp, "specular color %f %f %f\n", &para.mat[i].scolor[0], &para.mat[i].scolor[1], &para.mat[i].scolor[2]);
        fscanf(fp, "material shine %f\n", &para.mat[i].shine);
    }
    //
    fscanf(fp, "%c", &ch);
    while(ch!= '\n') // skip documentation line
        fscanf(fp, "%c", &ch);

    printf ("Reading in %s (%d triangles). . .\n", FileName, para.trinum);

    para.tri.resize(para.trinum);
    for (int i=0; i<para.trinum; i++) // read triangles
    {
        for (int j=0;j<3;j++)
        {

            fscanf(fp, "v%c %f %f %f %f %f %f %d\n", &ch,
                 &(para.tri[i].v[j].pos[0]), &(para.tri[i].v[j].pos[1]), &(para.tri[i].v[j].pos[2]),
                 &(para.tri[i].v[j].norm[0]), &(para.tri[i].v[j].norm[1]), &(para.tri[i].v[j].norm[2]),
                 &(para.tri[i].v[j].matind));
            para.tri[i].color[j] =(unsigned char)(int)(255*(para.mat[para.tri[i].v[0].matind].dcolor[j]));//Since very vertex use the same material index, so here I take the first vertex as its color
         }
            fscanf(fp, "face normal %f %f %f\n", &(para.tri[i].fnorm[0]), &(para.tri[i].fnorm[1]),
                &(para.tri[i].fnorm[2]));
        }
    // get the max and min of the object in x y z
        for(int i = 0; i < 3; i++)
            para.pmax[i] = para.pmin[i] = para.tri[0].v[0].pos[i];
        for(int i = 0; i < para.trinum; i++)
        {
            for(int j = 0; j < 3; j++)
            {
                for(int k = 0; k < 3; k++)
                {
                    if(para.tri[i].v[j].pos[k] < para.pmin[k])
                        para.pmin[k] = para.tri[i].v[j].pos[k];
                    else if(para.tri[i].v[j].pos[k] > para.pmax[k])
                        para.pmax[k] = para.tri[i].v[j].pos[k];
                }
            }
        }

   para.distance = fmax(para.pmax[1] - para.pmin[1],para.pmax[0] - para.pmin[0])* sqrt(3.0);
   translate_scaler[0] = fmin(fmin(para.pmax[0] - para.pmin[0],para.pmax[1] - para.pmin[1]),para.pmax[2] -para.pmin[2]);
   translate_scaler[1] = translate_scaler[0];
   translate_scaler[2] = translate_scaler[0];
   nearer_scaler = para.distance;
   farer_scaler = para.distance;
    fclose(fp);
	modelviewmat.IdentityMatrix();
	GLfloat temp[16];
    getTMatrix(-(para.pmin[0]+para.pmax[0])/2,-(para.pmin[1]+para.pmax[1])/2,-(para.pmax[2]+para.pmin[2])/2-para.distance,temp);
	modelviewmat.MatrixMult(temp);
	projectionmat.IdentityMatrix();
	CalcProjectionMatrix();

    std::cout<<"SMReadfile:";
    for(int ii =0; ii<16; ii++)
       std::cout<<modelviewmat.m[ii]<<' ';
    std::cout<<std::endl;
}


MatrixDrawing::MatrixDrawing(QWidget *parent) :
    QGLWidget(QGLFormat(QGL::SampleBuffers), parent)
{
    setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
}

QSize MatrixDrawing::sizeHint() const
{
	return QSize(256, 256);
}

void MatrixDrawing::initializeGL()
{
    makeCurrent();
    LoadShaders();
    glShadeModel(GL_SMOOTH);
    glClearColor(1.0,1.0,1.0,0.0);
    glClearDepth(1.0);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);
}


void MatrixDrawing::paintGL()
{
	makeCurrent();
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);
    CalcProjectionMatrix();
    glColor3ub(uipara.color[0],uipara.color[1],uipara.color[2]);
    glPolygonMode(GL_FRONT,uipara.primitive);
    glFrontFace(uipara.facefront);
    glBegin(GL_TRIANGLES);
    for(int i=0;i<para.trinum;i++)
    {
		glVertex3f(para.tri[i].v[0].pos[0],para.tri[i].v[0].pos[1],para.tri[i].v[0].pos[2]);
        glVertex3f(para.tri[i].v[1].pos[0],para.tri[i].v[1].pos[1],para.tri[i].v[1].pos[2]);
        glVertex3f(para.tri[i].v[2].pos[0],para.tri[i].v[2].pos[1],para.tri[i].v[2].pos[2]);
	}
    glEnd();
    glCullFace(GL_BACK);
    //mshader.program.release();
	//mshader.program.removeAllShaders();
}


 void MatrixDrawing::resizeGL(int width, int height)
 {
	 makeCurrent();
     glViewport(0,0,width,height);
     glClearColor(0,0,0,0);
     projectionmat.IdentityMatrix();
     CalcProjectionMatrix();

     mshader.modelview_loc = mshader.program.uniformLocation("modelviewmat");
     mshader.projection_loc = mshader.program.uniformLocation("projectionmat");
     mshader.program.setUniformValue(mshader.modelview_loc,QMatrix4x4((const qreal*)modelviewmat.m).transposed());
     mshader.program.setUniformValue(mshader.projection_loc,QMatrix4x4((const qreal*)projectionmat.m).transposed());

     std::cout<<"SM:";
     for(int ii =0; ii<16; ii++)
        std::cout<<modelviewmat.m[ii]<<' ';
     std::cout<<std::endl;
     /*
     std::cout<<"shader projection";
     for(int ii =0; ii<15; ii++)
        std::cout<<projectionmat.m[ii]<<' ';
     std::cout<<std::endl;*/
 }

void MatrixDrawing::setfilename(const char *fn)
{

    readfile(fn);
   // std::cout << "Open File: " << fn << std::endl;
    //emit initpara(-10*translate_scaler[0], 10*translate_scaler[0], -10*translate_scaler[1], 10*translate_scaler[1], -10*translate_scaler[2], 10*translate_scaler[2], 0.1*nearer_scaler, 5*farer_scaler);
    //initializeGL();
	makeCurrent();
    resizeGL(width(), height());
   // paintGL();
    updateGL();
    /*
    glewInit();
        if (glewIsSupported("GL_VERSION_2_0"))
            printf("Ready for OpenGL 2.0\n");
        else {
            printf("OpenGL 2.0 not supported\n");
            exit(1);
        }*/

}

void MatrixDrawing::setstruct(bool clockwise, char view)
{
    if(clockwise==TRUE){
        uipara.facefront =GL_CW;
        std::cout << "Clockwise: " << clockwise << '\t' << "View: ";
    }
    else
        uipara.facefront = GL_CCW;
    switch(view){
    case 'p':
        uipara.primitive = GL_POINT;
        std::cout << "Point" << std::endl;
        break;
    case 'f':
        uipara.primitive = GL_LINE;
        std::cout << "Frame" << std::endl;
        break;
    case 's':
        uipara.primitive = GL_FILL;
        std::cout << "Solid" << std::endl;
        break;
    default:  std::cout << "Unknown" << std::endl; break;
    }

    resizeGL(width(), height());
    //paintGL();
    updateGL();
}

void MatrixDrawing::setcolor(int red, int green, int blue)
{
    uipara.color[0]=red;
    uipara.color[1]=green;
    uipara.color[2]=blue;

    resizeGL(width(), height());
    //paintGL();
    updateGL();
 //   setShaders();
    //std::cout << "Color: " << red << ", " << green << ", " << blue << std::endl;
}

void MatrixDrawing::settrans(float x, float y, float z)
{

    for(int i=0;i<3;i++)
    {
        translate[i]=0;
    }
    uipara.translation[0]=x;
    uipara.translation[1]=y;
    uipara.translation[2]=z;
    /*
    for(int i=0;i<3;i++)
    {
        translate[i] = (uipara.translation[0]-last_translate[0])*u[i]
                +(uipara.translation[1]-last_translate[1])*v[i]
                +(uipara.translation[2]-last_translate[2])*n[i];
   }
   */
    for(int i=0;i<3;i++)
    {
        translate[i] = uipara.translation[i]-last_translate[i];
   }
    last_translate[0]=x;
    last_translate[1]=y;
    last_translate[2]=z;

    GLfloat temp[16];
    getTMatrix(-translate[0],-translate[1],-translate[2],temp);
    modelviewmat.MatrixMult(temp);
    /*
    for(int ii = 12;ii<15;ii++)
        modelviewmat.m[ii]=0;
    getTMatrix(-(para.pmin[0]+para.pmax[0])/2,-(para.pmin[1]+para.pmax[1])/2,-(para.pmax[2]+para.pmin[2])/2-para.distance,temp2);
    modelviewmat.MatrixMult(temp2);
    modelviewmat.MatrixMult(temp1);*/
    resizeGL(width(), height());
    //paintGL();
    updateGL();
//    setShaders();

    //std::cout << "Translation: " << x << ", " << y << ", " << z << std::endl;
}

void MatrixDrawing::setrotat(float x, float y, float z)
{
    rotation[0] = x-uipara.rotation[0];
    rotation[1] = y-uipara.rotation[1];
    rotation[2] = z-uipara.rotation[2];
    uipara.rotation[0] = x;
    uipara.rotation[1] = y;
    uipara.rotation[2] = z;

    CameraRotation();

    resizeGL(width(), height());
   // paintGL();
    updateGL();
 //   setShaders();

    //std::cout << "Rotation: " << x << ", " << y << ", " << z << std::endl;
}

void MatrixDrawing::setclip(float nearer, float farer)
{
    uipara.nearer = nearer;
    uipara.farer = farer;

    resizeGL(width(), height());
    //paintGL();
    updateGL();
 //   setShaders();
    //std::cout << "Near: " << nearer << '\t' << "Far" << farer << std::endl;
}



void MatrixDrawing::LoadShaders()
{
    std::cout<< "LoadShaders()" << std::endl;
    /*
    char *vs = NULL,*fs = NULL;
    QGLShader vshader(QGLShader::Vertex);
    QGLShader fshader(QGLShader::Fragment);

    vs = textFileRead("vertexshader.txt");
    fs = textFileRead("fragshader.txt");

    vshader.compileSourceCode(vs);
    fshader.compileSourceCode(fs);

    vshader.log();
    fshader.log();

    if(mshader.program)
    {
        mshader.program.release();
        mshader.program.removeAllShaders();
    }
    else mshader.program = new QGLShaderProgram;
*/
    if(!mshader.program.isLinked())
    {
        if(!mshader.program.addShaderFromSourceCode(QGLShader::Vertex,
                                        "uniform mat4 modelviewmat, projectionmat;\n"
                                        "void main(void)\n"
                                        "{\n"
                                        "   gl_FrontColor = gl_Color;\n"
                                        "   gl_Position = projectionmat*modelviewmat*gl_Vertex;\n"
                                        "}\n"))
        {
            qWarning()<<"Vertex Shader source file"<<mshader.program.log();
        }
        if(!mshader.program.addShaderFromSourceCode(QGLShader::Fragment,
                                        "void main(void)\n"
                                        "{\n"
                                        "   gl_FragColor = gl_Color;"
                                        "}\n"))
        {
            qWarning()<<"Fragment Shader source file"<<mshader.program.log();
        }

        if(!mshader.program.link())
        {
            qWarning()<<"Shader Program Linker Error" << mshader.program.log();
        }
        mshader.program.bind();
    }
}
